''' Pykemon is fundamentally modal:
    when you are Strolling around the world, you are on a Map, and not in a Battle or the MainMenu or a MiniGame,
    although you can easily change to one of those Modes.
'''
import pygame
import pykelib
if pykelib.config.DEVEL: import modes_devel

class Mode(object):
    ''' subclasses must maintain their own class attribute CALLBACKS=[],
        and override draw and __call__ methods per their docstrings.
    '''
    
    pykelib.misc.def_abstract_methods(
        ('draw', 'subclasses should override this abstract method to blit sprites etc. to pykelib.MAIN.bg'),
        ('__call__', 'subclasses should override this abstract method to call self.mainloop, then return a ModeTransition object.'),
    )
    
    def __init__(self, **kw):
        self.__dict__.update(kw)
        self.running = False
        self.connect(pykelib.config.FULLSCREEN_KEY, pygame.display.toggle_fullscreen)
        self.connect(pykelib.config.QUIT_KEY, pykelib.MAIN.quit)
        self.return_value = None
    
    def connect(self, key, callback):
        self.CALLBACKS.append((key, callback))
    
    def connects(self, *sigs):
        for sig in sigs: self.connect(*sig)
    
    def _dispatch_events(self):
        ''' use pygame.event.get to determine which callbacks in self.CALLBACKS to call.
        '''
        events = pygame.event.get()
        event_types = [event.type for event in events]
        if pygame.locals.QUIT in event_types:
            raise StopIteration
        event_keys = [event.key for event in events if event.type == KEYDOWN] + [EVERY_ITER]
        for key, callback in self.CALLBACKS:
            if key in event_keys:
                callback()
    
    def _iter_mainloop(self):
        ''' contains the entire body of the mainloop in self.mainloop.
        '''
        pykelib.MAIN.clock.tick(pykelib.config.FPS)
        self._dispatch_events()
        pykelib.MAIN.bg.fill(pykelib.config.bg_color, (0, 0, pykelib.config.SCREEN_WIDTH, pykelib.config.SCREEN_HEIGHT))
        self.draw()
        pykelib.MAIN.screen.blit(self.bg, (0, 0))
        pygame.display.flip()
    
    def _return(self, return_value):
        ''' clear self.running and store return_value, causing self.__call__ to return return_value.
        '''
        assert hasattr(return_value, '__call__'), "Mode objects must return callables, such as other Modes"
        self.return_value = return_value
    
    def __call__(self):
        ''' call self._iter_mainloop until self.running is cleared.
        '''
        while self.return_value is None: self._iter_mainloop()
        return self.return_value
    

class AskSavedOrNew(Mode):
    ''' always the first Mode that Main calls, asks user to either choose a save file to open or start a new game.
        returns a Strolling Mode configured according to user's selection.
    '''
    CALLBACKS = []
    
    def __init__(self):
        ''' 
                            Badges   Pykemon   Time       Last Played
            New Game
            0123456789abcdef  42       999   42:42:42   99 May 42:42:42
            0123456789abcdef  42       999   42:42:42   99 May 42:42:42
            
        '''
        Mode.__init__(self, **kw)
        self.image, self.rect = load_image(my_path('ask_saved_or_new_bg.jpg'))
        self.saved = [Player(fn) for fn in glob.glob(Player._find('*'))]
        if self.saved:
            self.saved.sort(pykelib.constants.SAVED_GAME_SORTER)
            self.selection = 0
            self.selection_unit = pykelib.MAIN.font.size(' ')[1]
            pykelib.misc.draw_text(self.image, ([
                    '                Badges   Pykemon   Time       Last Played',
                    'New Game'
                ] + [
                    game.summary()
                    for game in self.saved
                ]),
                self.selection_unit,
            )
            pykelib.misc.draw_text(self.image,
                'Use the up and down arrows to select, then hit Enter/Return.',
                self.selection_unit, (pykelib.config.SCREEN_HEIGHT - self.selection_unit)
            )
            self.connects(
                (K_UP, self.up_arrow),
                (K_DOWN, self.down_arrow),
                (K_ENTER, self.enter),
            )
            if pykelib.config.DEVEL:
                self.connect(pykelib.constants.DEVEL_MODE_KEY, self.devel)
        else:
            # short circuit to new game if no saved games
            self.selection = -1
            self.enter()
    
    def devel(self):
        ''' signal my call method to return the Development Mode.
        '''
        self._return(modes_devel.Development(XXX))
    
    def _mod_selection(self, n):
        self.selection = (self.selection + n) % (len(self.saved) + 1)
    
    def up_arrow(self):
        ''' move selection up
            XXX scroll
        '''
        self._mod_selection(-1)
    
    def down_arrow(self):
        ''' move selection down
            XXX scroll
        '''
        self._mod_selection(1)
    
    def enter(self):
        ''' act on user's selection, signal self.mainloop to return, so that i can return the new Mode.
        '''
        if self.selection == -1: self._return(Strolling(XXX))
        else: self._return(Strolling(XXX))
    
    def draw(self):
        ''' draw the images and text
        '''
        pykelib.MAIN.bg.blit(self.image, self.rect)
        pykelib.MAIN.bg.blit(self.arrow_image, pygame.Rect(
                self.selection_unit,
                ((self.selection + 2) * self.selection_unit),
                self.arrow_image.width,
                self.arrow_image.height
            )
        )
    

class Update(Mode):
    ''' 
    '''
    CALLBACKS = []
    def __init__(self, url):
        self.url = url
        self.image, self.rect = pykelib.misc.load_image(XXX)
        pykelib.misc.draw_text(self.image, [
                'An update to Pykemon is available!',
                'Hit Y to install it now,'
                'hit N to continue a saved game',
                'or start a new game.'
            ],
            0, 0
        )
        self.connects(
            (K_y, self.update),
            (K_n, self.ason),
        )
    
    def draw(self):
        pykelib.MAIN.bg.blit(self.image, self.rect)
    
    def update(self):
        # XXX download and install self.url
        pykelib.MAIN.quit()
        # XXX start the new pykelib.config.PYW_DIR/pykemon.pyw
    
    def ason(self):
        self._return(AskSavedOrNew())

class Strolling(Mode):
    ''' Mode in which user is walking around in a Map, whether it's inside a building, on a beach, in a patch of grass, or in a cave.
        may return either a MainMenu if user presses pykelib.config.MENU_KEY, a Battle Mode if a wild pykemon is encountered,
        or another Strolling Mode if user passes through a "door"/entrance/exit/window.
    '''
    CALLBACKS = []
    
    def __init__(self, **kw):
        Mode.__init__(self, **kw)
        # XXX load and blit images of where-ever i am
        # XXX connect callbacks to allow motion
    
    def draw(self):
        ''' draw the tile's images
        '''
    
    def __call__(self):
        self.mainloop()
        if XXX: return Strolling(XXX)
        elif XXX: return Battle(XXX)
        else: return MainMenu(XXX)

class Battle(Mode):
    ''' user is Battling either a Char or a wild pykemon.
        returns a Strolling configured similar to the Strolling that returned this Mode if user wins,
        otherwise "warps" user to a "hospital" by returning a Strolling configured to the last one visited.
    '''
    CALLBACKS = []
    
    def __init__(self, **kw):
        Mode.__init__(self, **kw)
        # XXX load and blit images of opponent pykemon, my pykemon, etc.
        # XXX connect callbacks to allow selection in the battle menu
    
    def draw(self):
        ''' draw the images and text
        '''
    
    def __call__(self):
        self.mainloop()
        if XXX: return Strolling(XXX)
        else: return Strolling(XXX)

GAME_TIME_MODES = (Battle, Strolling)	# Modes for which Main increments MAIN.player.game_time

class MainMenu(Mode):
    ''' Mode in which user can view information about himself and his own pykemon,
        use some items and abilities, visit the FAQ,
        automatically dynamically update this program from the cheeseshop or my comcast ftp space, etc.
    '''
    CALLBACKS = []
    
    def __init__(self, **kw):
        Mode.__init__(self, **kw)
        # XXX load and blit images, text of the game menu
        # XXX connect callbacks to allow selection
    
    def draw(self):
        ''' draw the images and text
        '''
    
    def __call__(self):
        self.mainloop()
        if XXX: return Strolling(XXX)
        else: return EXIT_MODE_LOOP

